//
//  RenderbunnyViewController.m
//  Renderbunny
//
//  Created by Holmes Futrell on 1/20/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import <QuartzCore/QuartzCore.h>

#import "RenderbunnyViewController.h"
#import "EAGLView.h"

#import "gles20errors.h"
#import "matrix.h"
#import "matrix_helpers.h"
#import <math.h>

@interface RenderbunnyViewController ()
@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) CADisplayLink *displayLink;
- (BOOL)loadShaders;
- (void)drawHUD;
@end

@implementation RenderbunnyViewController

@synthesize animating, context, displayLink;

- (void)awakeFromNib
{
    EAGLContext *aContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
    
    if (!aContext)
    {
		NSLog(@"Could not initialize context!");
        aContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES1];
    }
    
    if (!aContext)
        NSLog(@"Failed to create ES context");
    else if (![EAGLContext setCurrentContext:aContext])
        NSLog(@"Failed to set ES context current");
    
	self.context = aContext;
	[aContext release];
	
    [(EAGLView *)self.view setContext:context];
    [(EAGLView *)self.view setFramebuffer];
    
    if ([context API] == kEAGLRenderingAPIOpenGLES2) {
		if (![self loadShaders]) {
			NSLog(@"Failed to load shaders");
		}
	}
		
	rb_check_error_simple();

	
	model1 = [[IndexedModel alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"knight-catmull3" ofType:@"obj"] ];
	[model1 mapBuffer: IMODEL_BUFFER_VERTEX		toAttributeLocation: [shader getLocationForAttribute:@"position"]];
	[model1 mapBuffer: IMODEL_BUFFER_UV			toAttributeLocation: [shader getLocationForAttribute:@"uv"]];
	[model1 createVertexArray];
	
	model2 = [[IndexedModel alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"blobby-low" ofType:@"obj"] ];
	[model2 mapBuffer: IMODEL_BUFFER_VERTEX			toAttributeLocation: [tangentShader getLocationForAttribute:@"position"]];
	[model2 mapBuffer: IMODEL_BUFFER_UV				toAttributeLocation: [tangentShader getLocationForAttribute:@"uv"]];
	[model2 mapBuffer: IMODEL_BUFFER_NORMAL			toAttributeLocation: [tangentShader getLocationForAttribute:@"normal"]];
	[model2 mapBuffer: IMODEL_BUFFER_TANGENT		toAttributeLocation: [tangentShader getLocationForAttribute:@"tangent"]];
	[model2 createVertexArray];
	
	rb_check_error_simple();

	
	texture = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"wood-tex" ofType:@"png"]
										flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat|kGLTextureGenerateMipMaps];
    
	ambient = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"knight_amb_occ" ofType:@"png"]
										flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat|kGLTextureGenerateMipMaps ];
	 
	normals = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"knight-normals-world" ofType:@"png"]
	 								flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat|kGLTextureGenerateMipMaps ];

	 
	
	/*texture = [[GLTexture alloc] initWithPVRTC: [[NSBundle mainBundle] pathForResource:@"wood-tex-4bpp-1024" ofType:@"pvrtc"]
										  size: 1024
										   bpp: 4 
										 flags: kGLTextureNoAlphaFlag];
    
	ambient = [[GLTexture alloc] initWithPVRTC: [[NSBundle mainBundle] pathForResource:@"knight_amb_occ-2bpp-1024" ofType:@"pvrtc"]
										  size: 1024
										   bpp: 2 
										flags: kGLTextureNoAlphaFlag ];
	
	normals = [[GLTexture alloc] initWithPVRTC: [[NSBundle mainBundle] pathForResource:@"knight-normals-world-4bpp-1024" ofType:@"pvrtc"]
										size: 1024 bpp: 4 flags: kGLTextureNoAlphaFlag];*/
	
	tangentMap = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"leather-normals" ofType:@"png"]
										flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat|kGLTextureGenerateMipMaps];

	crazy_amb = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"blobby" ofType:@"png"]
										flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat|kGLTextureGenerateMipMaps ];

	/*tangentMap = [[GLTexture alloc] initWithPVRTC: [[NSBundle mainBundle] pathForResource:@"leather-normals-2bpp-1024" ofType:@"pvrtc"]
											size: 1024
											 bpp: 2
										   flags: kGLTextureNoAlphaFlag];
	
	crazy_amb = [[GLTexture alloc] initWithPVRTC: [[NSBundle mainBundle] pathForResource:@"blobby-2bpp-1024" ofType:@"pvrtc"]
										   size: 1024
											bpp: 2
										  flags: kGLTextureNoAlphaFlag ];*/
	
	
	horizon = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"horizon" ofType:@"png"]
										  flags: kGLTextureFilterModeLinear|kGLTextureWrapModeClamp ];

	
	GLubyte whiteColor[4] = { 255, 255, 255, 255 };
	GLubyte blackColor[4] = { 0, 0, 0, 255 };
	font = [[GLFont alloc] initWithFontName:[[UIFont systemFontOfSize: 16] fontName] size: 16 color: whiteColor stroke: 1.0f strokeColor: blackColor];
	
	glClearDepthf(1.0f);
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
	glEnable(GL_CULL_FACE);	
	
	animating = FALSE;
    animationFrameInterval = 1;
    self.displayLink = nil;
	
	rb_check_error_simple();
	
}

- (void)dealloc
{

	[shader release];
	shader = nil;
    
    // Tear down context.
    if ([EAGLContext currentContext] == context)
        [EAGLContext setCurrentContext:nil];
    
    [context release];
    
    [super dealloc];
}

- (void)viewWillAppear:(BOOL)animated
{
    [self startAnimation];
    
    [super viewWillAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [self stopAnimation];
    
    [super viewWillDisappear:animated];
}

- (void)viewDidUnload
{
	[super viewDidUnload];
	

    // Tear down context.
    if ([EAGLContext currentContext] == context)
        [EAGLContext setCurrentContext:nil];
	self.context = nil;	
}

- (NSInteger)animationFrameInterval
{
    return animationFrameInterval;
}

- (void)setAnimationFrameInterval:(NSInteger)frameInterval
{
    /*
	 Frame interval defines how many display frames must pass between each time the display link fires.
	 The display link will only fire 30 times a second when the frame internal is two on a display that refreshes 60 times a second. The default frame interval setting of one will fire 60 times a second when the display refreshes at 60 times a second. A frame interval setting of less than one results in undefined behavior.
	 */
    if (frameInterval >= 1)
    {
        animationFrameInterval = frameInterval;
        
        if (animating)
        {
            [self stopAnimation];
            [self startAnimation];
        }
    }
}

- (void)startAnimation
{
    if (!animating)
    {
        CADisplayLink *aDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
        [aDisplayLink setFrameInterval:animationFrameInterval];
        [aDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        self.displayLink = aDisplayLink;
        
        animating = TRUE;
    }
}

- (void)stopAnimation
{
    if (animating)
    {
        [self.displayLink invalidate];
        self.displayLink = nil;
        animating = FALSE;
    }
}

- (void)notify {
	mode = mode+1;
	mode = mode % 2;
}

- (void)drawFrame
{
		
    [(EAGLView *)self.view setFramebuffer];
	rb_check_error_simple();

    // Replace the implementation of this method to do your own custom drawing.
	
	glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	rb_check_error_simple();
	
	static float rot = 0.0f;
	
	rot += 0.01;
		
	float aspect = [self view].bounds.size.height / [self view].bounds.size.width;
	
	mat4 projection = rb_mat4_frustum( -1, 1, -aspect, aspect, 2, 5000);
	
	if (mode == 0) {
		// Use shader program.
		[shader useProgram];
		rb_check_error_simple();
		
		mat4 modelView = rb_mat4_rotate( -3.14159f / 2.0f, 1, 0, 0 );
		modelView = rb_mat4_mul(rb_mat4_rotate( rot, 0, 1, 0 ), modelView);
		modelView = rb_mat4_mul(rb_mat4_translate(make_vec3(0,0,-10)), modelView);
		mat4 modelViewProjection = rb_mat4_mul(projection, modelView);		
		
		vec3 lightDir = normalize_vec3( make_vec3(1,1,1));
		
		glUniform3fv([shader locationForUniform:@"lightDir"], 1, lightDir.coords); 
		glUniform1f([shader locationForUniform:@"specular_power"], 75.0f); 
		//float N = 1.6;
		//float mf = 0.0f; //pow( ( N - 1.0 ) / ( N + 1.0 ), 2.0);
		//glUniform1f([shader locationForUniform:@"mf"], mf); 
		
		glUniformMatrix4fv( [shader locationForUniform:@"modelviewMatrix"], 1, 0, modelView.entries);
		rb_check_error_simple();
		glUniformMatrix4fv( [shader locationForUniform:@"modelviewProjectionMatrix"], 1, 0, modelViewProjection.entries);
		rb_check_error_simple();
				
		rb_check_error_simple();
		
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, ambient.descriptor);
		
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, normals.descriptor);
		
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_2D, texture.descriptor);
		
		rb_check_error_simple();
		
				
		glUniform1i([shader locationForUniform:@"ambientSampler"], 0);
		glUniform1i([shader locationForUniform:@"normalsSampler"], 1);
		glUniform1i([shader locationForUniform:@"textureSampler"], 2);
		
		rb_check_error_simple();
		
		[model1 bindVertexArray];
		[model1 draw];	
		[model1 unbindVertexArray];
		
		rb_check_error_simple();
		
	}
	else {	
		
		
		// Use shader program.
		[tangentShader useProgram];
		rb_check_error_simple();
		
		vec3 lightDir = normalize_vec3( make_vec3(1,1,1));
		
		mat4 modelView = rb_mat4_rotate( -3.14159f / 2.0f, 1, 0, 0 );
		modelView = rb_mat4_mul(rb_mat4_rotate( rot, 0, 1, 0 ), modelView);
		modelView = rb_mat4_mul(rb_mat4_rotate( rot / 5.0f, 1, 0, 0 ), modelView);
		modelView = rb_mat4_mul(rb_mat4_translate(make_vec3(0,0,-8)), modelView);
		mat4 modelViewProjection = rb_mat4_mul(projection, modelView);
		
		glUniformMatrix4fv( [tangentShader locationForUniform:@"modelviewMatrix"], 1, 0, modelView.entries);
		glUniformMatrix4fv( [tangentShader locationForUniform:@"modelviewProjectionMatrix"], 1, 0, modelViewProjection.entries);		
		glUniform3fv([tangentShader locationForUniform:@"lightDir"], 1, lightDir.coords); 	
		glUniform1f([tangentShader locationForUniform:@"mf"], 0.0f); 
		glUniform1f([tangentShader locationForUniform:@"specularPower"], 75.0f); 	
		glUniform3fv([tangentShader locationForUniform:@"diffuseColor"],	1,		make_vec3(0.45, 0.45, 0.45).coords);
		glUniform1f([tangentShader locationForUniform:@"ambientAmount"],	0.1); 	
		glUniform3fv([tangentShader locationForUniform:@"specularColor"],	1,		make_vec3(0.45, 0.45, 0.45).coords); 	
		glUniform3fv([tangentShader locationForUniform:@"fresnelColor"],	1,		make_vec3(0.55, 0.55, 0.75).coords); 	
		glUniform1i([tangentShader locationForUniform:@"normalsSampler"], 0);
		glUniform1i([tangentShader locationForUniform:@"textureSampler"], 1);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, tangentMap.descriptor);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, crazy_amb.descriptor);
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_2D, 0);


		[model2 bindVertexArray];
		[model2 draw];	
		[model2 unbindVertexArray];
		
		
	}
	
	[basicShader useProgram];
	rb_check_error_simple();
	
	[self drawHUD];
	
	count++;
	
#if MSAA
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
	glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
	glResolveMultisampleFramebufferAPPLE();	
#endif
	
    [(EAGLView *)self.view presentFramebuffer];

}

-(void)countFrames:(NSTimer *)timer {
	
	fps = count;
	count = 0;
	
}

- (void)drawHUD {
	
	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	
	mat4 projection = rb_mat4_ortho(0, [self view].bounds.size.width, [self view].bounds.size.height, 0, -10, 10);
	mat4 modelview  = rb_mat4_translate(make_vec3(10, 10, 0));
	
	mat4 modelviewProjection = rb_mat4_mul(projection, modelview);
	
	glUniformMatrix4fv( [basicShader locationForUniform:@"modelViewProjectionMatrix"], 1, 0, modelviewProjection.entries );
	glUniform1i([basicShader locationForUniform:@"sampler"], 0);
	glUniform4f([basicShader locationForUniform:@"alpha"], 1.0f, 1.0f, 1.0f, 1.0f);
	
	rb_check_error_simple();
	
	[GLFont mapVerticesToAttributeLocation: [basicShader getLocationForAttribute:@"position_in"] ];
	[GLFont mapTexCoordsToAttributeLocation: [basicShader getLocationForAttribute:@"uv_in"] ];	
	
	[font drawString:[NSString stringWithFormat:@"Frames per second: %d", fps]];
	
	rb_check_error_simple();
	
	glDepthMask(GL_TRUE);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);
	
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc. that aren't in use.
}

- (BOOL)loadShaders
{
	
	shader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"worldspace" ofType:@"vsh"]
										  fragmentShader: [[NSBundle mainBundle] pathForResource:@"worldspace" ofType:@"fsh"]];
	
	[shader linkProgram];

	tangentShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"tangentspace" ofType:@"vsh"]
										  fragmentShader: [[NSBundle mainBundle] pathForResource:@"tangentspace" ofType:@"fsh"]];
	
	[tangentShader linkProgram];
	
	
	basicShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"basic" ofType:@"vsh"]
										  fragmentShader: [[NSBundle mainBundle] pathForResource:@"basic" ofType:@"fsh"]];
	[basicShader linkProgram];
	
    // Get uniform locations.

	count = 0;
	fps = 0;
	[NSTimer scheduledTimerWithTimeInterval: 1 target: self selector:@selector(countFrames:) userInfo: nil repeats: YES];
	    
	rb_check_error_simple();
	
    return TRUE;
}

@end
